﻿using SdmxMl.Common;
using SdmxMl.Manager;
using SdmxMl.SmsImportCfg;
using SdmxMl.Structure;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;

namespace ChopPlugin
{
    /// <summary>All kind of submitted modification type</summary>
    public enum ModificationType
    {
        Mutation,
        Creation,
        Suppression,
        Ignore
    }

    /// <summary>
    /// Represents a modification demand
    /// </summary>
    public class Mutation
    {
        #region Constants

        const int indexColumnAntrag = 1;
        const int indexColumnModifType = 2;
        const int indexColumnZCode = 3;
        const int indexColumnType = 4;
        const int indexColumnTextDe = 5;
        const int indexColumnTextFr = 6;
        const int indexColumnTextIt = 7;
        const int indexColumnLang = 8;

        const int CODE_MAX_LENGTH = 6;

        const string MSG_ERR_CODE_CHANGE = " Kreationen und Löschungen von Codes (Titeln) werden nicht unterstützt. Bitte entfernen sie die Kreationen und Löschungen von Codes und starten den Import dieser Änderungsdatei erneut. ";
        const string MSG_ERR_LANG_TARGET = " Mit diesem Update können ausschliesslich die Sprachen FR und IT  verarbeitet werden. Bitte stellen Sie sicher, dass die Spalte ‚toUpdateLanguage’ entweder ‚FR’ oder ‚IT’ enthält und starten den Import erneut.";
        const string MSG_ERR_ANTRAG = "Antrag: ";
        const string MSG_UNKNOWN_MODIF_TYPE = " Unknown modification type: ";
        const string MSG_INVALID_CODE = " Invalid code: ";
        const string MSG_INVALID_ZCODE = " Invalid zcode: ";
        const string MSG_INVALID_CODE_NAME = " Code Name string contains new line";

        const string CODE_MODIF_MUTATE = "M";
        const string CODE_MODIF_SUPPRESS = "S";
        const string CODE_MODIF_CREATE = "C";

        const string CODE_TARGET = "T";

        #endregion

        #region Properties

        /// <summary>Informative antrag Nr </summary>
        public string Antrag { get; private set; }
        /// <summary>Informative antrag Nr </summary>
        public ModificationType Modif { get; private set; }
        /// <summary>Code expressed as in codelist </summary>
        public string Code { get; private set; }
        /// <summary>Targetted annotation type. If null or empty, the target ids the code itself </summary>
        public string AnnotationType { get; private set; }

        /// <summary>the target text or replacement text (according to target language) </summary>
        public MultiLang Text { get; private set; }

        /// <summary>Targetted language for modification </summary>
        public string TargetLangId { get; private set; }

        /// <summary>Compare language to use for identification check </summary>
        public string CompareLangId { get; private set; }

        #endregion

        #region Constructor

        /// <summary> C'tor for a mutation demand </summary>
        /// <param name="wh"> helper bind to datatable representing excel mutation sheet</param>
        /// <param name="row"> row index of the demand in table</param>
        public Mutation(WorkSheetHelper wh, int row)
        {
            // Fetch antrag id information
            Antrag = wh.GetCellContent(row, indexColumnAntrag);
 
            // Get type of requested modification
            string mutType = wh.GetCellContent(row, indexColumnModifType);
            switch (mutType)
            {
                case CODE_MODIF_MUTATE: Modif = ModificationType.Mutation; break;
                case CODE_MODIF_SUPPRESS: Modif = ModificationType.Suppression; break;
                case CODE_MODIF_CREATE: Modif = ModificationType.Creation; break;
                default: throw new ApplicationException(MSG_ERR_ANTRAG + Antrag + MSG_UNKNOWN_MODIF_TYPE + mutType );
            }

            // Code to operate on (given as CHOP ZCode)
            string zCode = wh.GetCellContent(row, indexColumnZCode);
            Code = GetCodeId(zCode);
            if (Code == null)
                throw new ApplicationException(MSG_ERR_ANTRAG + Antrag + MSG_INVALID_ZCODE + zCode );
            if (Code.Length > CODE_MAX_LENGTH)
                throw new ApplicationException(MSG_ERR_ANTRAG + Antrag + MSG_INVALID_CODE + Code );
            // Type of element to operate on (an annotation type or the code name)
            string type = wh.GetCellContent(row, indexColumnType);

            // Text in the 3 supported languages
            Text = new MultiLang();
            Text.UpdateForLanguage(LangSupport.GERMAN_ID, wh.GetCellContent(row, indexColumnTextDe));
            Text.UpdateForLanguage(LangSupport.FRENCH_ID, wh.GetCellContent(row, indexColumnTextFr));
            Text.UpdateForLanguage(LangSupport.ITALIAN_ID, wh.GetCellContent(row, indexColumnTextIt));

            // For code name, newline is not allowed
            if (type == CODE_TARGET)
            {
                foreach (string langId in LangSupport.SupportedLanguages)
                {
                    string txt = Text.GetForLanguage(langId);
                    if (txt.IndexOf('\r') >= 0 || txt.IndexOf('\n') >= 0)
                        throw new ApplicationException(MSG_ERR_ANTRAG + Antrag + MSG_INVALID_CODE_NAME);
                }   
            }
            else // Annotation types
                AnnotationType = ChopCodes.CreateAnnotationFromType(type).Type;

            // Targeted language can only be IT or FR
            string tg = wh.GetCellContent(row, indexColumnLang).ToLower();
            if (tg != LangSupport.ITALIAN_ID && tg != LangSupport.FRENCH_ID)
                throw new ApplicationException(MSG_ERR_ANTRAG + Antrag + MSG_ERR_LANG_TARGET);
            TargetLangId = tg.ToLower();
            
            // Determine compare language to use
            CompareLangId = Text.GetForLanguage(LangSupport.GERMAN_ID).Length > 0 ? LangSupport.GERMAN_ID : 
                TargetLangId == LangSupport.ITALIAN_ID ? LangSupport.FRENCH_ID : LangSupport.ITALIAN_ID;
            if (Modif == ModificationType.Suppression)
                CompareLangId = TargetLangId;

            // Now check for unnacepted cases (code added or removed)
            if (AnnotationType == null && 
                (Modif == ModificationType.Creation || Modif == ModificationType.Suppression))
                throw new ApplicationException(MSG_ERR_ANTRAG + Antrag + MSG_ERR_CODE_CHANGE);
            
        }

        #endregion

        #region ZCode

        /// <summary>Helper, extracts SDMX code from CHOP ZCode </summary>
        /// <param name="zCode">CHOP zcode notation</param>
        /// <returns>code id as presented in SDMX</returns>
        public static string GetCodeId(string zCode)
        {
            if (zCode.Length < 2 || zCode[0] != 'Z')
                return null;
            return zCode.Substring(1).Replace(".", string.Empty);
        }
        #endregion

    }

    /// <summary>
    /// Class for managing CHOP modifications list
    /// </summary>
    public class ModificationList
    {
        #region Internal Members

        /// <summary>Container of requested modifications </summary>
        private List<Mutation> mutationList;

        public List<CodeListType> ModifiedCodelists { get; set; }

        #endregion

        #region Constants

        /// <summary>Expected sheet name holding modification list</summary>
        const string ExcelModifSheetName = "Blatt1";
        const string MSG_PATH_MISSING = "Please provides path. for Excel 2003 CHOP modification list";
        const string MSG_COLUMN_COUNT = "Invalid CHOP mutation list, column count is too small";
        const string MSG_SHEET_NAME = "CHOP modifications must be placed in an Excel sheet named: ";
        const string MSG_CANNOT_LOCATE_CODE = "Unable to locate SDMX code: ";
        const string MSG_CODE_NAME_NO_MATCH = "Unexpected code text does not match for code: ";
        const string MSG_ANNOT_NOT_FETCH = "Unable to fetch annotation ";
        const string MSG_CL_FINALIZED = "Cannot operate on finalized codelist: ";
        const string MSG_ANTRAG = " Antrag: ";
        const string MSG_FOR_CODE = " for code: ";


        const int ColumnCount = 8;

        #endregion

        #region Constructor

        /// <summary>C'tor</summary>
        /// <param name="path">path to Excel 2003 Chop modification file</param>
        public ModificationList(string path)
        {
            mutationList = new List<Mutation>();
            ModifiedCodelists = new List<CodeListType>();
            Parse(LoadModifList(path));
        }

        /// <summary>Load Excel file content as a datatable</summary>
        /// <param name="path">path to Excel 2003 Chop modification file</param>
        /// <returns>a DataTable holding non checked modifications</returns>
        private DataTable LoadModifList(string path)
        {
            ExcelDataReader.ExcelDataReader spreadsheet = null;
            if (path == null)
                throw new ApplicationException(MSG_PATH_MISSING);

            FileStream fs = null;
            try
            {
                fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
                spreadsheet = new ExcelDataReader.ExcelDataReader(fs);
            }
            finally
            {
                if (fs != null)
                    fs.Close();
            }
            if (spreadsheet != null)
            {
                if (spreadsheet.WorkbookData.Tables.Contains(ExcelModifSheetName) == false)
                    throw new ApplicationException(MSG_SHEET_NAME + ExcelModifSheetName);
            }

            return spreadsheet.WorkbookData.Tables[ExcelModifSheetName];
        }

        /// <summary>
        /// Scan requested modifications and check syntax validity
        /// </summary>
        /// <param name="dt">Table holding non checked modifications requests</param>
        private void Parse(DataTable dt)
        {
            WorkSheetHelper wh = new WorkSheetHelper(dt);

            // Ensure eenough columns
            if (dt.Columns.Count < ColumnCount)
                throw new ApplicationException(MSG_COLUMN_COUNT);

            for (int row = 2; row <= dt.Rows.Count; row++)
            {
                Mutation mut = new Mutation(wh, row);
                mutationList.Add(mut);
            }
        }
        #endregion

        #region Apply modifications

        /// <summary>
        /// Apply modifications to submitted CHOP hierarchy
        /// </summary>
        /// <param name="hcl">the hierarchicalcodelist representing a CHOP version</param>
        /// <param name="operate">True, locate targets and operate modifications, False locate only</param>
        public void Apply(HierarchicalCodelistType hcl, bool operate)
        {
            // Get cache
            SmManager sm = ReferenceManager.GetSmContainerImage();
            ModifiedCodelists = new List<CodeListType>();

            //Fetch all referred codelists
            CodeListType[] arrClt = new CodeListType[hcl.CodelistRefList.Count];
            for (int i = 0; i < hcl.CodelistRefList.Count; i++ )
                arrClt[i] = sm.GetArtefact(SdmxArtefactType.CodeLists, hcl.CodelistRefList[i]) as CodeListType;

            foreach (Mutation mut in mutationList)
            {
                // Codelists are ordered according to code size
                int index = mut.Code.Length - 1;
                // Record codelists that are modified
                if (ModifiedCodelists.Contains(arrClt[index]) == false)
                {
                    // If codelist is finalized, throw an error
                    if (arrClt[index].IsFinal)
                        throw new ApplicationException(MSG_CL_FINALIZED + arrClt[index].FullIdent);
                    ModifiedCodelists.Add(arrClt[index]);
                }

                // Locate code for applying mutation
                ISdmxHierarchyListItem item = arrClt[index].CodeList.LocateItem(mut.Code);
                if (item == null)
                    throw new ApplicationException(MSG_CANNOT_LOCATE_CODE + mut.Code);

                if (mut.AnnotationType != null)
                {
                    if (mut.Modif == ModificationType.Creation)
                    {
                        if (operate) // Create a new annotation
                        {
                            Annotation a = new Annotation(mut.AnnotationType);
                            a.Text.UpdateForLanguage(mut.TargetLangId, mut.Text.GetForLanguage(mut.TargetLangId));
                            item.AnnotationList.Add(a);
                        }
                    }
                    else
                    {
                        Annotation a = LocateTargetAnnotation(item.AnnotationList, mut);
                        if (a == null)
                            throw new ApplicationException(MSG_ANNOT_NOT_FETCH + mut.AnnotationType + MSG_FOR_CODE
                                + mut.Code + MSG_ANTRAG + mut.Antrag);
                        if (operate)
                        {
                            if (mut.Modif == ModificationType.Mutation)
                                a.Text.UpdateForLanguage(mut.TargetLangId, mut.Text.GetForLanguage(mut.TargetLangId));

                            else if (mut.Modif == ModificationType.Suppression)
                            {
                                // Clear for target lang
                                a.Text.UpdateForLanguage(mut.TargetLangId, string.Empty);

                                // all empty now ?, remove annotation
                                if (a.Text.IsEmpty)
                                    item.AnnotationList.Remove(a);
                            }
                        }
                    }
                    
                }
                else
                {
                    // Code text change, compare lang text must match
                if (item.Name.GetForLanguage(mut.CompareLangId) != mut.Text.GetForLanguage(mut.CompareLangId))
                    throw new ApplicationException(MSG_CODE_NAME_NO_MATCH + mut.Code + MSG_ANTRAG + mut.Antrag);
                if (operate)
                    item.Name.UpdateForLanguage(mut.TargetLangId, mut.Text.GetForLanguage(mut.TargetLangId));

                } 
            }
        }
        #endregion

        #region Helper

        /// <summary> Helper for location of targeted annotation </summary>
        /// <param name="allAnnot"> item full annotation list</param>
        /// <param name="mut">requested modification</param>
        /// <returns>loacated annotation or null if not found </returns>
        private Annotation LocateTargetAnnotation(AnnotationList allAnnot, Mutation mut)
        {
            // All annotations of required type
            AnnotationList anl = allAnnot.GetAllAnnotationOfType(mut.AnnotationType, string.Empty);

            foreach (Annotation a in anl)
                if (a.Text.GetForLanguage(mut.CompareLangId) == mut.Text.GetForLanguage(mut.CompareLangId))
                    return a;
            return null;
        }
        #endregion

    }
}
